namespace Ex
{
    using KeraLua;

    public class ExCsFunctionRef : IExCsFunction
    {
        protected EnvLua env;

        public EnvLua CurrentLua
        {
            get { return env; }
        }

        private readonly LuaFunction luaFunction;

        public ExCsFunctionRef(EnvLua env, LuaFunction func)
        {
            this.env = env;
            this.luaFunction = func;
        }

        public static ExCsFunctionRef WatchFromLuaStack(EnvLua env, int index)
        {
            lock (env)
            {
                return new ExCsFunctionRef(env, env.Current.ToCFunction(index));
            }
        }

        public static ExCsFunctionRef FetchFromLuaStack(EnvLua env)
        {
            lock (env)
            {
                var func = env.Current.ToCFunction(-1) ?? throw new Exception("unexpected error: value here should be csfunction");
                env.Current.Pop(1);
                return new ExCsFunctionRef(env, func);
            }
        }

        public IndefiniteVarType Call(UsableLuaType[] result_type, params object[] args)
        {
            return CallFromArray(result_type, args);
        }

        public IndefiniteVarType CallFromArray(UsableLuaType[] result_type, object[] args)
        {
            lock (env)
            {
                var lua = env.Current;
                var param = new IndefiniteVarType(args);
                lua.PushCFunction(luaFunction);
                param.PushToLuaStack(env);
                var stats = lua.PCall(param.Count, result_type.Length, 0);
                if (stats != LuaStatus.OK)
                {
                    throw new Exception("call csharp function error:" + lua.ToString(-1));
                }
                return IndefiniteVarType.FetchFromLuaStack(env, result_type);
            }
        }

        public IndefiniteVarType CallFromArray(int return_num, object[] args)
        {
            lock (env)
            {
                var lua = env.Current;
                var param = new IndefiniteVarType(args);
                lua.PushCFunction(luaFunction);
                param.PushToLuaStack(env);
                var stats = lua.PCall(param.Count, return_num, 0);
                if (stats != LuaStatus.OK)
                {
                    throw new Exception("call csharp function error:" + lua.ToString(-1));
                }
                return IndefiniteVarType.FetchFromLuaStack(env, return_num);
            }
        }
        
        public void PushToLuaStack()
        {
            lock (env)
            {
                env.Current.PushCFunction(luaFunction);
            }
        }
        
    }
}